#if !defined(PrototypeHeader)
#define PrototypeHeader

#include <tr1/unordered_map>

#include "atom.h"
#include "node.h"
#include "reference.h"
#include "tree.h"
#include "tuple.h"

// Stores some "matchable" attributes of a node for proper indexing.
// Basically follows the predicate format like (write / 2).
class Prototype {
public:
  Prototype () : // A loose-reference
    functor (0), type (Reference::ID), u (0) {
  }
  Prototype (char *o) : // Operator
    functor (0), type (Tree::ID), u (o) {
  }
  Prototype (Atom *a) : // Single atom
    functor (a), type (Atom::ID), u (1) {
  }
  Prototype (Atom *a, int l) : // Fixed-tuple with functor
    functor (a), type (Tuple::ID), u (l) {
  }

  std::string functorName ();

  bool operator < (const Prototype &p2) const {
    if(functor == p2.functor)
      if(type == p2.type)
        switch(type) {
        case Tree::ID:
          return u.ops[0] != p2.u.ops[0] ? u.ops[0] < p2.u.ops[0] : u.ops[1]
              < p2.u.ops[1];
        case Tuple::ID:
          return u.length < p2.u.length;
        default:
          return false;
        }
      else
        return type < p2.type;
    else
      return functor < p2.functor;
  }

  bool operator == (const Prototype &p2) const {
    if(type == p2.type)
      if(type == Tree::ID)
        return u.ops[0] == p2.u.ops[0] && u.ops[1] == p2.u.ops[1];
      else
        return functor == p2.functor && u.length == p2.u.length;
    else
      return false;
  }

  bool operator != (const Prototype &p2) const {
    return !(*this == p2);
  }

  size_t hash () const {
    return type + u.length + (size_t) functor;
  }

  Prototype static get (Node&);
  Prototype static getFromRule (Node&);

  Atom *functor;
  Node::NodeType type;

  union U {
    U () :
      length (0) {
    }
    U (int l) :
      length (l) {
    }
    U (char *o) {
      length = 0; // Clear other bytes to make the hash unique
      ops[0] = o[0];
      ops[1] = o[1];
    }
    int length;
    char ops[2];
  };
  U u;
};

namespace std {
namespace tr1 {
template<> struct hash<Prototype> {
  size_t operator() (const Prototype& proto) const {
    return proto.hash ();
  }
};
}
}

#endif
